﻿#include "pch.h"
#include "sbcs.h"
#if !_SB_WINDOWS_
#include <dirent.h>
#endif

//////////////////////////////////////////////////////////////////////////
// 디렉터리

// 디렉터리 구조체.
struct kDir
{
#if _SB_WINDOWS_
	kwchar*				name;
	int					stat;
	HANDLE				handle;
	WIN32_FIND_DATA		ffd;
	char				file[MAX_PATH];
#else
	DIR*				pd;
	kwchar				ufile[260];
#endif
};

/**
 * 디렉터리를 새로 만든다.
 * @param	path 	디렉터리의 완전한 경로 이름.
 * @param	flags	사용하지 않음.
 * @return	문제가 있거나 실패하면 널값을 반환, 성공할 때 반환값은 만들어진 개체.
 */
kDir* k_dir_new(const char* path, int flags)
{
	kDir* self;

	// 안쓰네...
	K_NOUSE(flags);

#if _SB_WINDOWS_
	ksize_t len;
	kwchar* suffix;
	kwchar fpath[MAX_PATH];
	kwchar uni[MAX_PATH];
	WIN32_FILE_ATTRIBUTE_DATA fad;

	k_return_value_if_fail(path != NULL, NULL);

	k_conv_utf8_to_utf16(uni, MAX_PATH - 1, path, 0);

	if (!GetFileAttributesEx(uni, GetFileExInfoStandard, &fad))
		return NULL;

	if (!(fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
		return NULL;

	_wfullpath(fpath, uni, MAX_PATH);

	suffix = k_new(k_wcslen(fpath) + 1/*슬래시*/ + 1/*서픽스*/ + 1/*널*/, kwchar);
	k_wcscpy(suffix, fpath);
	len = k_wcslen(suffix);

	if (suffix[0] != L'\0' && suffix[len - 1] != L'/' && suffix[len - 1] != L'\\')
	{
		suffix[len] = K_L(K_PATH_SEP);
		suffix[len + 1] = L'\0';
		len++;
	}

	suffix[len] = L'*';
	suffix[len + 1] = L'\0';

	self = k_new_0(kDir);
	self->name = suffix;
	self->handle = INVALID_HANDLE_VALUE;
#else
	DIR* pd;

	k_return_value_if_fail(path, NULL);

	pd = opendir(path);
	k_return_value_if_fail(pd != NULL, NULL);

	self = k_new_0(kDir);
	self->pd = pd;
#endif

	return self;
}

/**
 * 디렉터리를 새로 만든다. (유니코드 사용)
 * @param	path 	디렉터리의 완전한 경로 이름.
 * @param	flags	사용하지 않음.
 * @return	문제가 있거나 실패하면 널값을 반환, 성공할 때 반환값은 만들어진 개체.
 */
kDir* k_dir_new_l(const kwchar* path, int flags)
{
	kDir* self;

	// 헐... 안써
	K_NOUSE(flags);

#if _SB_WINDOWS_
	ksize_t len;
	kwchar* suffix;
	kwchar fpath[MAX_PATH];
	WIN32_FILE_ATTRIBUTE_DATA fad;

	k_return_value_if_fail(path != NULL, NULL);

	if (!GetFileAttributesEx(path, GetFileExInfoStandard, &fad))
		return NULL;

	if (!(fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
		return NULL;

	_wfullpath(fpath, path, MAX_PATH);

	suffix = k_new(k_wcslen(fpath) + 1/*슬래시*/ + 1/*서픽스*/ + 1/*널*/, kwchar);
	k_wcscpy(suffix, fpath);
	len = k_wcslen(suffix);

	if (suffix[0] != L'\0' && suffix[len - 1] != L'/' && suffix[len - 1] != L'\\')
	{
		suffix[len] = K_L(K_PATH_SEP);
		suffix[len + 1] = L'\0';
		len++;
	}

	suffix[len] = L'*';
	suffix[len + 1] = L'\0';

	self = k_new_0(kDir);
	self->name = suffix;
	self->handle = INVALID_HANDLE_VALUE;
#else
	DIR* pd;
	char asc[260];

	k_return_value_if_fail(path, NULL);

	k_conv_uni_to_utf8(asc, 260 - 1, path, 0);
	pd = opendir(asc);
	k_return_value_if_fail(pd != NULL, NULL);

	self = k_new_0(kDir);
	self->pd = pd;
#endif

	return self;
}

/**
 * 디렉터리 개체 제거.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 */
void k_dir_delete(kDir* self)
{
#if _SB_WINDOWS_
	if (self->handle != INVALID_HANDLE_VALUE)
		FindClose(self->handle);

	k_delete(self->name);
#else
	closedir(self->pd);
#endif

	k_delete(self);
}

/**
 * 디렉터리에서 항목 읽기.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	문제가 있거나 실패하면 널값을 반환, 성공할 때 반환값은 파일 이름.
 */
const char* k_dir_read(kDir* self)
{
#if _SB_WINDOWS_
	for (;;)
	{
		if (self->stat < 0)
			return NULL;
		else if (self->stat == 0)
		{
			// 처음
			self->handle = FindFirstFileEx(self->name, FindExInfoStandard, &self->ffd, FindExSearchNameMatch, NULL, 0);
			self->stat = self->handle == INVALID_HANDLE_VALUE ? -1 : 1;
		}
		else
		{
			// 계속
			if (FindNextFile(self->handle, &self->ffd))
				self->stat++;
			else
			{
				FindClose(self->handle);
				self->handle = INVALID_HANDLE_VALUE;
				self->stat = -1;

				return NULL;
			}
		}

		if (k_wcseqv(self->ffd.cFileName, L".") ||
			k_wcseqv(self->ffd.cFileName, L".."))
			continue;

		k_conv_utf16_to_utf8(self->file, MAX_PATH - 1, self->ffd.cFileName, 0);

		return self->file;
	}
#else
	struct dirent* ent;

	ent = readdir(self->pd);

	while (ent && (k_streqv(ent->d_name, ".") || k_streqv(ent->d_name, "..")))
		ent = readdir(self->pd);

	return ent ? ent->d_name : NULL;
#endif
}

/**
 * 디렉터리에서 항목 읽기. (유니코드)
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	문제가 있거나 실패하면 널값을 반환, 성공할 때 반환값은 파일 이름.
 */
const kwchar* k_dir_read_l(kDir* self)
{
#if _SB_WINDOWS_
	for (;;)
	{
		if (self->stat < 0)
			return NULL;
		else if (self->stat == 0)
		{
			// 처음
			self->handle = FindFirstFileEx(self->name, FindExInfoStandard, &self->ffd, FindExSearchNameMatch, NULL, 0);
			self->stat = self->handle == INVALID_HANDLE_VALUE ? -1 : 1;
		}
		else
		{
			// 계속
			if (FindNextFile(self->handle, &self->ffd))
				self->stat++;
			else
			{
				FindClose(self->handle);
				self->handle = INVALID_HANDLE_VALUE;
				self->stat = -1;

				return NULL;
			}
		}

		if (k_wcseqv(self->ffd.cFileName, L".") ||
			k_wcseqv(self->ffd.cFileName, L".."))
			continue;

		return self->ffd.cFileName;
	}
#else
	struct dirent* ent;

	ent = readdir(self->pd);

	while (ent && (k_streqv(ent->d_name, ".") || k_streqv(ent->d_name, "..")))
		ent = readdir(self->pd);

	if (!ent)
		return NULL;
	else
	{
		k_conv_utf8_to_uni(self->ufile, 260 - 1, ent->d_name, 0);
		return self->ufile;
	}
#endif
}

/**
 * 디렉터리를 첫 항목으로 감기.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 */
void k_dir_rewind(kDir* self)
{
#if _SB_WINDOWS_
	if (self->handle != INVALID_HANDLE_VALUE)
		FindClose(self->handle);

	self->handle = INVALID_HANDLE_VALUE;
	self->stat = 0;
#else
	rewinddir(self->pd);
#endif
}

/**
 * 디렉터리에서 몇번째 항목인지 얻기.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	항목 순서.
 */
int k_dir_tell(kDir* self)
{
#if _SB_WINDOWS_
	return self->stat;
#elif _SB_ANDROID_
	return -1;
#else
	return (int)telldir(self->pd);
#endif
}

/**
 * 디렉터리에서 순서 항목으로 찾아가지.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @param	pos			찾아갈 위치.
 */
void k_dir_seek(kDir* self, int pos)
{
#if _SB_WINDOWS_
	if (pos < 0)
	{
		if (self->handle != INVALID_HANDLE_VALUE)
			FindClose(self->handle);

		self->handle = INVALID_HANDLE_VALUE;
		self->stat = -1;
	}
	else
	{
		k_dir_rewind(self);

		while ((self->stat < pos) && k_dir_read(self))
			;
	}
#elif _SB_ANDROID_
	int cnt = 0;
	rewinddir(self->pd);

	while (cnt < pos && readdir(self->pd))
		cnt++;

#else
	seekdir(self->pd, pos);
#endif
}
